package com.xwn.common.database.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInterceptor;
import com.xwn.common.database.properties.DatabaseProperties;
import org.apache.ibatis.logging.log4j.Log4jImpl;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import java.io.IOException;
import java.util.*;

/**
 * @Author: xwn
 * @Date: 2020/4/17 0:04
 */
@Configuration
public class EnableDatabaseConfiguration {
    private static final Logger log = LoggerFactory.getLogger(EnableDatabaseConfiguration.class);

    @Autowired
    DatabaseProperties properties;

    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DruidDataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(properties.jdbcDriver);
        ds.setUrl(properties.jdbcUrl);
        ds.setUsername(properties.jdbcUsername);
        ds.setPassword(properties.jdbcPassword);
        ds.setInitialSize(properties.jdbcInitialSize);
        ds.setMinIdle(properties.jdbcMinIdle);
        ds.setMaxActive(properties.jdbcMaxActive);
        ds.setTestWhileIdle(true);
        ds.setValidationQuery("SELECT 'x' FROM DUAL");
        ds.setTestOnBorrow(false);
        ds.setTestOnReturn(false);
        ds.setMaxWait(60000);
        ds.setMinEvictableIdleTimeMillis(300000);
        ds.setTimeBetweenEvictionRunsMillis(60000);
        ds.setPoolPreparedStatements(true);
        ds.setTransactionQueryTimeout(120000);
        return ds;
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
        DataSourceTransactionManager dtm = new DataSourceTransactionManager();
        dtm.setDataSource(getDataSource());
        log.info("----------------加载数据库配置---------------");
        return dtm;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sf = new SqlSessionFactoryBean();
        sf.setTypeAliasesPackage("com.xwn.**.entity");
        sf.setDataSource(getDataSource());
        Properties pro = new Properties();
        sf.setPlugins(new org.apache.ibatis.plugin.Interceptor[]{pageInterceptor()});
        pro.setProperty("dialect", properties.jdbcType);
        sf.setConfigurationProperties(pro);
        if (properties.jdbcMapperConfigPath != null && !"".equals(properties.jdbcMapperConfigPath)) {
            sf.setConfiguration(buildConfiguration());
        } else {
            sf.setConfigLocation(new ClassPathResource(properties.jdbcMapperConfigPath));
        }
        parseMapperLocations(sf);
        return sf;
    }

    private void parseMapperLocations(SqlSessionFactoryBean sf) throws IOException {
        List<Resource> resources =new ArrayList<>();
        for(String location:properties.jdbcMapperLocations.split(";")) {
            final Resource[] tmpResources = new PathMatchingResourcePatternResolver().getResources(location);
            for(Resource r : tmpResources){
                if(!resources.contains(r))
                    resources.add(r);
            }
        }
        sf.setMapperLocations(resources.toArray(new Resource[resources.size()]));
    }

    /**
     * 默认mybatis配置
     * @return
     */
    private org.apache.ibatis.session.Configuration buildConfiguration() {
        org.apache.ibatis.session.Configuration conf = new org.apache.ibatis.session.Configuration();
        //这个配置使全局的映射器启用或禁用 缓存
        conf.setCacheEnabled(true);
        //全局启用或禁用延迟加载。当禁用时, 所有关联对象都会即时加载
        conf.setLazyLoadingEnabled(true);
        //允许或不允许多种结果集从一个单独 的语句中返回(需要适合的驱动)
        conf.setMultipleResultSetsEnabled(true);
        //使用列标签代替列名。 不同的驱动在这 方便表现不同。 参考驱动文档或充分测 试两种方法来决定所使用的驱动
        conf.setUseColumnLabel(true);
        //允许 JDBC 支持生成的键。 需要适合的 驱动。 如果设置为 true 则这个设置强制 生成的键被使用, 尽管一些驱动拒绝兼 容但仍然有效(比如Derby)
        conf.setUseGeneratedKeys(false);
        //配置默认的执行器。SIMPLE 执行器没 有什么特别之处。REUSE 执行器重用 预处理语句。BATCH 执行器重用语句 和批量更新
        conf.setDefaultExecutorType(ExecutorType.REUSE);
        //设置超时时间, 它决定驱动等待一个数 据库响应的时间
        conf.setDefaultStatementTimeout(120000);
        conf.setSafeRowBoundsEnabled(false);
        conf.setMapUnderscoreToCamelCase(false);
        conf.setLocalCacheScope(LocalCacheScope.SESSION);
        conf.setJdbcTypeForNull(JdbcType.OTHER);
        conf.setLogPrefix("dao.");
        conf.setLogImpl(Log4jImpl.class);
        Set<String> methods = new HashSet<>(4);
        methods.add("equals");
        methods.add("clone");
        methods.add("hashCode");
        methods.add("toString");
        conf.setLazyLoadTriggerMethods(methods);
        return conf;
    }

    /**  分页组件配置类
     * helperDialect：分页插件会自动检测当前的数据库链接，自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时，可以使用下面的缩写值：
     oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
     特别注意：使用 SqlServer2012 数据库时，需要手动指定为 sqlserver2012，否则会使用 SqlServer2005 的方式进行分页。
     你也可以实现 AbstractHelperDialect，然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。
     offsetAsPageNum：默认值为 false，该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时，会将 RowBounds 中的 offset 参数当成 pageNum 使用，可以用页码和页面大小两个参数进行分页。
     rowBoundsWithCount：默认值为false，该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时，使用 RowBounds 分页会进行 count 查询。
     pageSizeZero：默认值为 false，当该参数设置为 true 时，如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果（相当于没有执行分页查询，但是返回结果仍然是 Page 类型）。
     reasonable：分页合理化参数，默认值为false。当该参数设置为 true 时，pageNum<=0 时会查询第一页， pageNum>pages（超过总数时），会查询最后一页。默认false 时，直接根据参数进行查询。
     params：为了支持startPage(Object params)方法，增加了该参数来配置参数映射，用于从对象中根据属性名取值， 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable，不配置映射的用默认值， 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
     supportMethodsArguments：支持通过 Mapper 接口参数来传递分页参数，默认值false，分页插件会从查询方法的参数值中，自动根据上面 params 配置的字段中取值，查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。
     autoRuntimeDialect：默认值为 false。设置为 true 时，允许在运行时根据多数据源自动识别对应方言的分页 （不支持自动选择sqlserver2012，只能使用sqlserver），用法和注意事项参考下面的场景五。
     closeConn：默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时，会自动获取一个数据库连接， 通过该属性来设置是否关闭获取的这个连接，默认true关闭，设置为 false 后，不会关闭获取的连接，这个参数的设置要根据自己选择的数据源来决定。
     * @return
     */
    @Bean
    public PageInterceptor pageInterceptor(){
        PageInterceptor pageInterceptor =new PageInterceptor();
        Properties pro=new Properties();
        pro.setProperty("Properties",properties.jdbcType);
        pageInterceptor.setProperties(pro);
        return pageInterceptor;
    }
}
